#version 460
layout (local_size_x=1) in;
layout (binding=0, rgba8) uniform image2D output_texture;
layout (binding=1, rgba8) uniform image2D input_texture;
float camera_pos_z = 0.0;

struct Ray { 
    vec3 start; // origin of the ray
    vec3 dir; // normalized direction of the ray
};

struct Collision { 
    float t; // distance along ray at which this collision occurs
    vec3 p; // world position of the collision
    vec3 n; // surface normal at the collision point
    bool inside; // whether the ray started inside of the object and collided while exiting
    int object_index ; // index of the object this collision hit
};

float sphere_radius = 0.5;
vec3 sphere_position = vec3(0.0, 0.0, -1.0);
vec3 sphere_color = vec3(1.0, 0.0, 0.0); 

float sphere_radius1 = 100;
vec3 sphere_position1 = vec3(0.0, -100.5, -1.0);
vec3 sphere_color1 = vec3(1.0, 0.0, 0.0); 

vec3 box_mins = vec3(-2.0,-2.0, 0.0);
vec3 box_maxs = vec3(-0.5, 1.0, 2.0);
vec3 box_color = vec3(1.0, 0.0, 0.0); // box color is red
// ------------------------------------------------------------------------------
// Checks if Ray r intersects the box
// ------------------------------------------------------------------------------
Collision intersect_box_object(Ray r) { // Calculate the box's world mins and maxs:
    vec3 t_min = (box_mins - r.start) / r.dir;
    vec3 t_max = (box_maxs - r.start) / r.dir;
    vec3 t_minDist = min(t_min, t_max);
    vec3 t_maxDist = max(t_min, t_max);
    float t_near = max(max(t_minDist.x, t_minDist.y), t_minDist.z);
    float t_far = min(min(t_maxDist.x, t_maxDist.y), t_maxDist.z);
    Collision c;
    c.t = t_near;
    c.inside = false;

    // If the ray didn't intersect the box, return a negative t value
    if (t_near >= t_far || t_far <= 0.0) { 
        c.t = -1.0;
        return c;
    }

    float intersect_distance = t_near;
    vec3 plane_intersect_distances = t_minDist;
    // if t_near < 0, then the ray started inside the box and left the box
    if (t_near < 0.0) { 
        c.t = t_far;
        intersect_distance = t_far;
        plane_intersect_distances = t_maxDist;
        c.inside = true;
    }

    // Checking which boundary the intersection lies on
    int face_index = 0;
    if (intersect_distance == plane_intersect_distances.y) face_index = 1;
    else if (intersect_distance == plane_intersect_distances.z) face_index = 2;
    // Create the collision normal
    c.n = vec3(0.0);
    c.n[face_index] = 1.0;
    // If we hit the box from the negative axis, invert the normal
    if (r.dir[face_index] > 0.0) c.n *= -1.0;
    // Calculate the world-position of the intersection:
    c.p = r.start + c.t * r.dir;
    return c;
}
// ------------------------------------------------------------------------------
// Checks if Ray r intersects the sphere
// ------------------------------------------------------------------------------
Collision intersect_sphere_object(Ray r) { 
    float qa = dot(r.dir, r.dir);
    float qb = dot(2*r.dir, r.start-sphere_position);
    float qc = dot(r.start-sphere_position, r.start-sphere_position) - sphere_radius*sphere_radius;
    // Solving for qa * t^2 + qb * t + qc = 0
    float qd = qb * qb - 4 * qa * qc;
    Collision c;
    c.inside = false;
    if (qd < 0.0) // no solution in this case
    { 
        c.t = -1.0;
        return c;
    }
    float t1 = (-qb + sqrt(qd)) / (2.0 * qa);
    float t2 = (-qb - sqrt(qd)) / (2.0 * qa);
    float t_near = min(t1, t2);
    float t_far = max(t1, t2);
    c.t = t_near;
    if (t_far < 0.0) { 
        c.t = -1.0;
        return c;
    }

    if (t_near < 0.0)  { 
        c.t = t_far;
        c.inside = true;
    }

    c.p = r.start + c.t * r.dir; // world position of the collision
    c.n = normalize(c.p - sphere_position); // use the world position to compute the surface normal
    if (c.inside)  { 
        c.n *= -1.0;
    }
    return c;
}
//------------------------------------------------------------------------------
// Returns the closest collision of a ray
// object_index == -1 if no collision
// object_index == 1 if collision with sphere
// object_index == 2 if collision with box
//------------------------------------------------------------------------------
Collision get_closest_collision(Ray r) { 
    Collision closest_collision, cSph, cBox;
    closest_collision.object_index = -1;
    cSph = intersect_sphere_object(r);
    //cBox = intersect_box_object(r);

    if (cSph.t > 0) { 
        closest_collision = cSph;
        closest_collision.object_index = 1;
    }

    // if ((cBox.t > 0) && ((cBox.t < cSph.t) || (cSph.t < 0))) { 
    //     closest_collision = cBox;
    //     closest_collision.object_index = 2;
    // }
    return closest_collision;
}
// ------------------------------------------------------------------------------
// This function casts a ray into the scene and returns the final color for a pixel
// ------------------------------------------------------------------------------
vec3 raytrace(Ray r) { 
    Collision c = get_closest_collision(r);
    float t = 0.5*(r.dir.y + 1.0);
    
    if (c.object_index == -1) return (1.0-t)*vec3(1.0, 1.0, 1.0) + t*vec3(0.5, 0.7, 1.0); //sky
    if (c.object_index == 1) {
        vec3 N = c.n;
        return 0.5 * vec3(N.x+1, N.y+1, N.z+1);
        //return sphere_color;
    }
    //if (c.object_index == 2) return box_color;
}
void main() { 
    int width = int(gl_NumWorkGroups.x); // one workgroup = one invocation = one pixel
    int height = int(gl_NumWorkGroups.y);
    ivec2 pixel = ivec2(gl_GlobalInvocationID.xy);

    float aspect_ratio = float(width) / height;
    float viewport_height = 2.0;
    float viewport_width = aspect_ratio * viewport_height;
    float focal_length = 1.0;

    vec3 origin = vec3(0, 0, 0);
    vec3 horizontal = vec3(viewport_width, 0, 0);
    vec3 vertical = vec3(0, viewport_height, 0);
    vec3 lower_left_corner = origin - horizontal/2 - vertical/2 - vec3(0, 0, focal_length);

    // Get this pixel's world-space ray
    Ray ray;
    ray.start = vec3(0.0, 0.0, camera_pos_z);
    float u = float(pixel.x) / width;
    float v = float(pixel.y) / height;
    ray.dir =  lower_left_corner + u*horizontal + v*vertical - origin;

    vec3 color = raytrace(ray);
    imageStore(output_texture, pixel, vec4(color, 1.0));
}